package com.erbao.io.netty.refactor.rpc.proxy;

import com.erbao.io.netty.refactor.rpc.Dispatcher;
import com.erbao.io.netty.refactor.rpc.protocol.MyContent;
import com.erbao.io.netty.refactor.transport.ClientFactory;
import com.erbao.io.netty.refactor.transport.TransportTypeEnum;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * @author CaoBaoYin
 * @since 2023/3/31 7:41
 */
public class MyProxy {
    static final Dispatcher dispatcher = Dispatcher.dispatcher;
    public static <T> T proxyGet(Class<T> interfaceClazz) {
        Object instance = Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz}, new InvocationHandler() {
            /**
             *
             * @param proxy the proxy instance that the method was invoked on
             *
             * @param method the {@code Method} instance corresponding to
             * the interface method invoked on the proxy instance.  The declaring
             * class of the {@code Method} object will be the interface that
             * the method was declared in, which may be a superinterface of the
             * proxy interface that the proxy class inherits the method through.
             *
             * @param args an array of objects containing the values of the
             * arguments passed in the method invocation on the proxy instance,
             * or {@code null} if interface method takes no arguments.
             * Arguments of primitive types are wrapped in instances of the
             * appropriate primitive wrapper class, such as
             * {@code java.lang.Integer} or {@code java.lang.Boolean}.
             *
             * @return
             * @throws Throwable
             * proxy 是代理类
             * method 就是接口对应的方法
             * args 就是接口方法的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 接口中的任何方法调用都会调用invoke
                System.out.println("进入代理");
                System.out.println("proxy: "+proxy.getClass().getName()+" method: "+method.getName()+"args:");
                // toString 会造成递归调用
                // System.out.println("proxy:".concat(proxy.toString()).concat(" method:").concat(method.toString()).concat(" args:").concat(args.toString()));
                // method.invoke(proxy, args) 这么写还是会调用当前方法

                /*判断是本机调用还是远程调用，因为服务端启动会进行注册，以便分发调用到对应的类
                * 如果查询不到注册类，说明客户端和服务端是两台jvm，需要远程调用；
                * 如果能查到注册类，说明使用的是一台jvm（因为dispatcher是单例，jvm只会加载一次），使用的是一个dispatcher，本地调用即可*/
                Object result ;//返回结果
                Object object = dispatcher.get(interfaceClazz.getName());
                if (Objects.isNull(object)) {
                    // rpc 远程调用 就像小火车拉货，content是货，header就是小火车。 content具体的数据，还需要header层完成IO的传输
                    // 设计consumer调用provider的过程
                    // 1.调用服务，方法，参数 ==》 封装message (content 即body)
                    String interfaceName = interfaceClazz.getName();
                    String methodName = method.getName();
                    Class<?>[] parameterTypes = method.getParameterTypes();

                    MyContent content = new MyContent();
                    content.setName(interfaceName);
                    content.setMethodName(methodName);
                    content.setArgs(args);
                    content.setParameterTypes(parameterTypes);

                    // 传输数据
                    // CompletableFuture<Object> completableFuture = ClientFactory.INSTANCE.transport(content);
                    CompletableFuture<Object> completableFuture = ClientFactory.INSTANCE.transport(content, TransportTypeEnum.HTTP);
                    // 5.走IO流 响应了对应同一个请求，代码回到对应位置再继续执行
                    result = completableFuture.get();
                } else {
                    /*本地调用
                     * 使用代理，可以插入一些插件的机会（例如日志、统计调用次数等），做一些扩展*/
                    System.out.println("local FC...");
                    result = method.invoke(object, args);
                }

                return result;
            }
        });
        return (T) instance;
    }


}
